﻿/****************************************************************************************
 * Copyright © 2020 - 2023 by AngryKeyboard.
 * SFML_俄罗斯方块 is licensed under MulanOWL BY v1.
 * You can use this software according to the terms and conditions of the MulanOWL BY v1.
 * You may obtain a copy of MulanOWL BY v1 at:
 *          http://license.coscl.org.cn/MulanOWLBYv1
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the MulanOWL BY v1 for more details.
****************************************************************************************/
#include <iostream>
#include "CAutoFill.h"

//静态变量初始化
double CAutoFill::weight[elementCount] = { 45, 32, -32, -93, -79, -34, 12 };    //各参数的权重
//double CAutoFill::weight[elementCount] = { 9000, 32, -32, -93, -79, -2000 };    //各参数的权重
double CAutoFill::values[elementCount] = { 0 };    //每一项的值
double CAutoFill::m_dMaxResultValue = -999999999;            //最终结果（先设置一个很小的值，程序运行会比较大小，找出最大值）

CBlock CAutoFill::m_TempBlock;//运算中用到的方块
int CAutoFill::m_TempTable[ROW_COUNT][COL_COUNT];//运算中用到的桌面


void CAutoFill::fillToButtom() {
  //移动到底部
  m_TempBlock.dropToBottom(m_TempTable);

  //固化方块到桌面
  int blockIndex = m_TempBlock.Shape();//获取方块编号
  const SPoint *BlockPos = m_TempBlock.Pos();//获取坐标点
  for (int i = 0; i < 4; i++) {
    int row = BlockPos[i].row;
    int col = BlockPos[i].column;
    //检查行列值，避免数组访问越界
    if (row >= 0 && row < ROW_COUNT && col >= 0 && col < COL_COUNT) {
      // 设置标记，“固化”对应位置
      m_TempTable[row][col] = blockIndex + 1;//标记设置为非0值
    }
  }
}

int CAutoFill::clearLines() {
  int nClearLines = 0;

  for (int i = 0; i < ROW_COUNT; i++) {
    int count = 0;
    for (int j = 0; j < COL_COUNT; j++) {
      if (m_TempTable[i][j]) {
        count++;
      }
    }

    if (count < COL_COUNT) {
      //没有满行就继续扫描下一行
      continue;
    }

    //满行时进行消行
    //如果满行是最顶部的那行，就清零。
    if (i == 0) {
      memset(m_TempTable, 0, COL_COUNT * sizeof(int));
    }
    else {
      memmove(m_TempTable + 1, m_TempTable, i * COL_COUNT * sizeof(int));
    }

    nClearLines += 1;//记录消除的行数
  }
  return nClearLines;
}

void CAutoFill::moveToTop() {
  int maxRow = -99999;
  const SPoint *pos = m_TempBlock.Pos();
  //遍历每个坐标点
  for (int i = 0; i < BLOCK_POINT_COUNT; i++) {
    //找出四个坐标点，最靠近下方的row坐标
    if (pos[i].row > maxRow) {
      maxRow = pos[i].row;
    }
  }
  m_TempBlock.move(DOWN, -maxRow);//向上移动到顶部
}

void CAutoFill::rotate(int times) {
  m_TempBlock.rotateClockwise();//旋转
  m_TempBlock.m_nDirection += CLOCKWISE;        //刷新方向数据
  m_TempBlock.m_nDirection += DIRECTION_COUNT;        //保证方向数据不为负数
  m_TempBlock.m_nDirection = m_TempBlock.m_nDirection % DIRECTION_COUNT;  //保证方向数据不超过指定范围
}

CBlock *CAutoFill::getBestBlock(const CBlock &curBlock, const int table[ROW_COUNT][COL_COUNT]) {
  //CBlock preBestBlocks[DIRECTION_COUNT] = { curBlock, curBlock, curBlock, curBlock };//每个方向最好的方块数据
  /************
  初始化所有变量
  ************/
  CBlock preBestBlock(curBlock);//预选方块
  m_dMaxResultValue = -999999999;
  memset(values, 0, sizeof(values));
  m_TempBlock = curBlock;//获取当前方块的信息
  memcpy_s(m_TempTable, sizeof(m_TempTable), table, sizeof(m_TempTable));//初始化桌面


  //优化方向检查
  int dir_count = DIRECTION_COUNT;//方向检查次数设置
  if (m_TempBlock.Shape() == O) {
    //O型方块不旋转，所以只扫描一次
    dir_count = 1;
  }
  if (
    m_TempBlock.Shape() == I
    || m_TempBlock.Shape() == S
    || m_TempBlock.Shape() == Z
    ) {
    //某些方块只扫描两个方向
    dir_count = 2;
  }

  //开始遍历各个方向
  for (int dir = 0; dir < dir_count; dir++) {
    //先把方块移动到最左边
    m_TempBlock.moveLeftRight(m_TempTable, -COL_COUNT);
    //扫描每个位置
    for (int col = 0; col < COL_COUNT; col++) {
      /*************
      控制方块落到底部
      *************/
      memcpy_s(m_TempTable, sizeof(m_TempTable), table, sizeof(m_TempTable));//初始化桌面
      fillToButtom();//方块固化到底部

      /********
      处理数据
      ********/
      double curValue = getResultValue();
      if (m_dMaxResultValue < curValue) {
        m_dMaxResultValue = curValue; //保存当前最佳值
        preBestBlock = m_TempBlock;   //保存这个方块的状态
      }

      /****************
      控制方块回到界面顶部
      ****************/
      moveToTop();//把方块移动到游戏区顶部
      m_TempBlock.moveLeftRight(m_TempTable, 1);//向右移动一次方块
    }

    //选出当前方向最好的位置后
    moveToTop();//把方块移动到游戏区顶部
    rotate();//并旋转一次，再继续查找
  }

  return new CBlock(preBestBlock);
}

double CAutoFill::getLandingHeight() {
  const SPoint *pos = m_TempBlock.Pos();//获取方块坐标
  double sum = 0;//row坐标总和
  for (int i = 0; i < BLOCK_POINT_COUNT; i++) {
    sum += pos[i].row;
  }

  return sum / BLOCK_POINT_COUNT;//返回row坐标的平均值
}

double CAutoFill::getErodedPieceCells() {
  const SPoint *pos = m_TempBlock.Pos();//获取方块坐标
  int count = 0;//统计参与消行的点数
  for (int i = 0; i < BLOCK_POINT_COUNT; i++) {
    int curRow = pos[i].row;
    int curCol;
    //遍历这一整行table中的所有元素
    for (curCol = 0; curCol < COL_COUNT; curCol++) {
      //如果这一行有空的
      if (m_TempTable[curRow][curCol] == 0) {
        break;//提前终止curCol循环
      }
    }

    //判断curCol循环是否是提前终止
    if (curCol < COL_COUNT) {
      continue;//该行不会被消除，不计数，扫描下一行
    }

    count++;//满行计数
  }

  return count;
}

double CAutoFill::getRowTransitions() {
  int cnt = 0;//统计变化次数
  for (int row = 0; row < ROW_COUNT; row++) {
    for (int col = 0; col < COL_COUNT - 1; col++) {
      //这两个格子中，如果其中一个为空格，且其中一个为非空格（两个格子不一样）
      if (!(m_TempTable[row][col] && m_TempTable[row][col + 1]) && (m_TempTable[row][col] || m_TempTable[row][col + 1])) {
        cnt++;
      }
    }
  }

  return cnt;
}

double CAutoFill::getColTransitions() {
  int cnt = 0;//统计变化次数
  for (int col = 0; col < COL_COUNT; col++) {
    for (int row = 0; row < ROW_COUNT - 1; row++) {
      //这两个格子中，如果其中一个为空格，且其中一个为非空格（两个格子不一样）
      if (!(m_TempTable[row][col] && m_TempTable[row + 1][col]) && (m_TempTable[row][col] || m_TempTable[row + 1][col])) {
        cnt++;
      }
    }
  }

  return cnt;
}

double CAutoFill::getHoleSum() {
  int cnt = 0;  //统计洞的数量
  bool flag;    //记录上一次是否遇到方块
  for (int col = 0; col < COL_COUNT; col++) {
    flag = false;//初始化状态,表示当前没有遇到方块
    for (int row = 0; row < ROW_COUNT; row++) {
      //如果遇到方块
      if (m_TempTable[row][col]) {
        flag = true;
      }
      else//否则检查上一次有没有遇到方块
      {
        //如果上次遇到方块，但这次不是方块
        if (flag == true) {
          //洞数加1，并重置flag
          cnt++;
          flag = false;
        }
      }
    }
  }

  return cnt;
}

double CAutoFill::getWellSum() {
  int sum = 0;//所有井的总值
  int cnt = 0;//单个井的深度
  //int well[21] = { 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105,120, 136, 153, 171, 190, 210 };        //井的打表

  for (int col = 1; col < COL_COUNT; col++) {
    for (int row = 0; row < ROW_COUNT; row++) {
      bool leftBorder;
      bool rightBorder;

      //判断左边是否为边界
      if (col - 1 < 0) {
        //左边界设为真，方便后面对井的检查
        leftBorder = true;
      }
      else {
        //根据桌面实际情况判断取值
        leftBorder = m_TempTable[row][col - 1];
      }
      //判断右边是否为边界
      if (col + 1 >= COL_COUNT) {
        //右边界设为真，方便后面对井的检查
        rightBorder = true;
      }
      else {
        //根据桌面实际情况判断取值
        rightBorder = m_TempTable[row][col + 1];
      }

      //当前格子为空，且左边有格子，且右边有格子（当前格子是井的内部）
      if (!m_TempTable[row][col] && leftBorder && rightBorder) {
        //井深加1
        cnt++;
      }
      else//如果不是井了，就把之前一个井的值加起来
      {
        //sum += well[cnt];
        while (cnt) {
          sum += cnt;
          cnt--;
        }
        //cnt = 0;
      }
    }
  }

  return sum;
}

void CAutoFill::renewValues() {
  values[landingHeight] = getLandingHeight();
  values[erodedPieceCells] = getErodedPieceCells();

  //消行以后获取其它数据
  values[removeLines] = clearLines();
  values[rowTransitions] = getRowTransitions();
  values[colTransitions] = getColTransitions();
  values[holeSum] = getHoleSum();
  values[wellSum] = getWellSum();
}

double CAutoFill::getResultValue() {
  renewValues();
  double result = 0;
  for (int i = 0; i < elementCount; i++) {
    result += (weight[i] * values[i]);
  }
  //针对消行数进行特殊计算
  //result += pow(values[removeLines], weight[removeLines]);
  int tmp1 = COL_COUNT - values[landingHeight];
  int tmp2 = pow(weight[removeLines], 5);
  result += pow(values[removeLines], tmp2);
  //针对消行方块数进行特殊计算
  //result += pow(values[erodedPieceCells], weight[erodedPieceCells]);

  //针对井深进行特殊计算
  //result -= pow(values[wellSum], 2);

  return result;
}
